package com.ccc.dingtalk.util;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.ccc.dingtalk.config.DingtalkProperties;
import com.ccc.dingtalk.constant.CheckTypeEnum;
import com.ccc.dingtalk.constant.LocationResultEnum;
import com.ccc.dingtalk.constant.SourceTypeEnum;
import com.ccc.dingtalk.constant.TimeResultEnum;
import com.ccc.dingtalk.pojo.db.UserInfo;
import com.ccc.dingtalk.pojo.dingtalk.DingDept;
import com.ccc.dingtalk.pojo.dingtalk.DingShift;
import com.ccc.dingtalk.pojo.dingtalk.DingUser;
import com.ccc.dingtalk.pojo.vo.AttendanceRecordresultVO;
import com.ccc.dingtalk.pojo.vo.ClockTimeVO;
import com.ccc.dingtalk.service.db.IUserInfoService;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiAttendanceListRequest;
import com.dingtalk.api.request.OapiAttendanceShiftListRequest;
import com.dingtalk.api.request.OapiAttendanceShiftQueryRequest;
import com.dingtalk.api.request.OapiGettokenRequest;
import com.dingtalk.api.request.OapiUserListadminRequest;
import com.dingtalk.api.request.OapiUserListidRequest;
import com.dingtalk.api.request.OapiUserListsimpleRequest;
import com.dingtalk.api.request.OapiV2DepartmentListsubRequest;
import com.dingtalk.api.request.OapiV2UserGetRequest;
import com.dingtalk.api.response.OapiAttendanceListResponse;
import com.dingtalk.api.response.OapiAttendanceShiftListResponse;
import com.dingtalk.api.response.OapiAttendanceShiftQueryResponse;
import com.dingtalk.api.response.OapiGettokenResponse;
import com.dingtalk.api.response.OapiUserListadminResponse;
import com.dingtalk.api.response.OapiUserListidResponse;
import com.dingtalk.api.response.OapiUserListsimpleResponse;
import com.dingtalk.api.response.OapiV2DepartmentListsubResponse;
import com.dingtalk.api.response.OapiV2UserGetResponse;
import com.taobao.api.ApiException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 旧版服务端工具类
 *
 * @author superC
 * @version 2.0.0
 * @date 2023/8/7
 */

@Component
public class DingtalkUtil {
    protected static final Logger logger = LoggerFactory.getLogger(DingtalkUtil.class);

    private final long offset = 0L;
    private final long limit = 50L;

    @Autowired
    private DingtalkProperties dingtalkProperties;

    @Autowired
    private IUserInfoService userInfoService;

    /**
     * 获取access_token
     * GET https://oapi.dingtalk.com/gettoken?appkey=appkey&appsecret=appsecret
     *
     * @return token封装对象
     */
    public String getAccessToken() throws Exception {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken");
        OapiGettokenRequest request = new OapiGettokenRequest();
        request.setAppkey(dingtalkProperties.getAppKey());
        request.setAppsecret(dingtalkProperties.getAppSecret());
        request.setHttpMethod("GET");
        OapiGettokenResponse response = client.execute(request);
        if (response != null && "ok".equals(response.getErrmsg())) {
            logger.info("【调用钉钉接口】-> TOKEN：{}", response.getAccessToken());
            return response.getAccessToken();
        }
        throw new RuntimeException(Objects.requireNonNull(response).getErrmsg());
    }

    /**
     * 获取部门列表
     *
     * @param accessToken 凭证
     */
    public List<DingDept> getDeptList(String accessToken) {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/department/listsub");
        OapiV2DepartmentListsubRequest req = new OapiV2DepartmentListsubRequest();
        OapiV2DepartmentListsubResponse rsp = null;
        try {
            rsp = client.execute(req, accessToken);
            if (rsp.getResult() == null) {
                throw new RuntimeException(rsp.getErrmsg());
            }
            if (rsp.getResult().size() == 0) {
                logger.error("【调用钉钉接口】-> 获取部门列表为空！");
            }
            List<OapiV2DepartmentListsubResponse.DeptBaseResponse> result = rsp.getResult();
            return result.stream().map(dept -> {
                DingDept dingDept = new DingDept();
                BeanUtils.copyProperties(dept, dingDept);
                return dingDept;
            }).collect(Collectors.toList());
        } catch (ApiException e) {
            logger.error("【调用钉钉接口】-> 【获取钉钉部门数据异常】\r\n异常记录：", e);
        }
        return new ArrayList<>();
    }

    /**
     * 获取当前部门下的所有用户基础列表
     *  本接口只支持获取员工的userId和name两个字段信息，如需获取部门下员工详情信息，请参考获取部门用户详情。
     *
     * @param accessToken 凭证
     * @param deptId      部门ID
     */
    public List<DingUser> getUserInfo(String accessToken, Long deptId) {
        try {
            DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/user/listsimple");
            OapiUserListsimpleRequest req = new OapiUserListsimpleRequest();
            req.setDeptId(deptId);
            req.setCursor(offset);
            req.setSize(limit);
            OapiUserListsimpleResponse rsp = client.execute(req, accessToken);
            OapiUserListsimpleResponse.PageResult result = rsp.getResult();
            if (result == null) {
                throw new RuntimeException(rsp.getErrmsg());
            }
            List<OapiUserListsimpleResponse.ListUserSimpleResponse> list = result.getList();
            return list.stream().map(user -> {
                DingUser dingUser = new DingUser();
                dingUser.setUserid(user.getUserid());
                dingUser.setName(user.getName());
                return dingUser;
            }).collect(Collectors.toList());
        } catch (ApiException e) {
            logger.error("【调用钉钉接口】-> 【获取钉钉用户数据异常】\r\n异常记录：", e);
        }
        return new ArrayList<>();
    }

    /**
     * 获取当前部门下的所有用户userid列表
     *
     * @param accessToken 凭证
     * @param deptId      部门ID
     */
    public List<String> getUserIds(String accessToken, Long deptId) {
        try {
            DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/user/listid");
            OapiUserListidRequest req = new OapiUserListidRequest();
            req.setDeptId(deptId);
            OapiUserListidResponse rsp = client.execute(req, accessToken);
            if (rsp.getResult() == null) {
                throw new RuntimeException(rsp.getErrmsg());
            }
            OapiUserListidResponse.ListUserByDeptResponse result = rsp.getResult();
            return result.getUseridList();
        } catch (ApiException e) {
            logger.error("【调用钉钉接口】-> 【获取钉钉用户数据异常】\r\n异常记录：", e);
        }
        return new ArrayList<>();
    }

    /*
     * 获取考勤列表
     * @param access_token凭证
     * @param workDateFrom
     * @param workDateTo
     * @param userIds
     * @return
     * @throws ApiException
     */
    public List<AttendanceRecordresultVO> getAttendanceList(String accessToken, String workDateFrom, String workDateTo, List<String> userIds) throws ApiException {
        OapiAttendanceListRequest req = new OapiAttendanceListRequest();
        req.setWorkDateFrom(workDateFrom);
        req.setWorkDateTo(workDateTo);
        req.setUserIdList(userIds);
        req.setOffset(offset);
        req.setLimit(limit);
        return getAttendanceList(accessToken, req);
    }

    /*
     * 获取考勤列表
     *
     * @param accessToken 凭证
     * @param offset
     * @param workDateFrom
     * @param workDateTo
     * @param deptId
     * @return
     * @throws ApiException
     */
    public List<AttendanceRecordresultVO> getAttendanceList(String accessToken, long offset, String workDateFrom, String workDateTo, Long deptId) throws ApiException {
        OapiAttendanceListRequest req = new OapiAttendanceListRequest();
        req.setWorkDateFrom(workDateFrom);
        req.setWorkDateTo(workDateTo);
        req.setUserIdList(getUserIds(accessToken, deptId));
        req.setOffset(offset);
        req.setLimit(limit);
        return getAttendanceList(accessToken, req);
    }

    private List<AttendanceRecordresultVO> getAttendanceList(String accessToken, OapiAttendanceListRequest req) throws ApiException {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/attendance/list");
        OapiAttendanceListResponse rsp = client.execute(req, accessToken);
        if (rsp.getRecordresult() == null) {
            throw new RuntimeException(rsp.getErrmsg());
        }
        List<OapiAttendanceListResponse.Recordresult> resultList = rsp.getRecordresult();
        logger.info("【调用钉钉接口】-> 获取到钉钉的打卡记录个数为 {} 条", resultList.size());

        // 获取DB中的用户信息
        List<UserInfo> userInfoList = userInfoService.findAll();

        return resultList.stream().map(recordresult -> {
            AttendanceRecordresultVO vo = new AttendanceRecordresultVO();
            BeanUtils.copyProperties(recordresult, vo);
            vo.setTimeResult(recordresult.getTimeResult());
            vo.setUserCheckTime(recordresult.getUserCheckTime());

            vo.setSourceTypeName(SourceTypeEnum.getLabel(vo.getSourceType()));
            vo.setLocationResultName(LocationResultEnum.getLabel(vo.getLocationResult()));
            vo.setTimeResultName(TimeResultEnum.getLabel(vo.getTimeResult()));
            vo.setCheckTypeName(CheckTypeEnum.getLabel(vo.getCheckType()));
            // 获取用户名称
            vo.setUserName(findUserName(accessToken, recordresult.getUserId(), userInfoList));
//            pringClockInfo(vo);
            return vo;
        }).sorted(Comparator.comparing(AttendanceRecordresultVO::getUserCheckTime).reversed())
                .collect(Collectors.toList());
    }

    private String findUserName(String accessToken, String userId, List<UserInfo> userInfoList) {
        // 1. 先从DB获取用户姓名
        String userName = "";
        if (!userInfoList.isEmpty()) {
            userName = userInfoList.stream().filter(userInfo -> userId.equals(userInfo.getId())).map(UserInfo::getName).findFirst().orElse("");
        }
        if (StrUtil.isNotBlank(userName)) {
            return userName;
        }
        try {
            logger.warn("【调用钉钉接口】-> 用户【{}】在DB中不存在，重新从钉钉中获取用户！", userId);
            // 2. 从钉钉获取用户姓名
            userName = getUserName(accessToken, userId);
            // 3. 再存入DB中
            userInfoService.insert(new UserInfo(userId, userName));
        } catch (ApiException e) {
            logger.error("【调用钉钉接口】-> 【获取用户详细信息异常】\r\n异常记录：", e);
        }
        return userName;
    }

    private void pringClockInfo(AttendanceRecordresultVO vo) {
        StringBuilder sb = new StringBuilder();
        sb.append("用户：【").append(vo.getUserId()).append(", ").append(vo.getUserName())
                .append("】; 考勤类型：【").append(vo.getCheckType()).append(", ").append(vo.getCheckTypeName())
                .append("】; 数据来源：【").append(vo.getSourceType()).append(", ").append(vo.getSourceTypeName())
                .append("】; 打卡结果：【").append(vo.getTimeResult()).append(", ").append(vo.getTimeResultName())
                .append("】; 打卡结果：【").append(vo.getTimeResult()).append(", ").append(vo.getTimeResultName())
        ;
        if (vo.getUserCheckTime() != null) {
            sb.append("】; 实际打卡时间：【").append(DateUtil.format(vo.getUserCheckTime(), DatePattern.NORM_DATETIME_PATTERN)).append("】；");
        } else {
            sb.append("】; 实际打卡时间：【").append(" - 】；");
        }
        logger.info("【调用钉钉接口】-> 获取到钉钉的打卡记录为：{}", sb.toString());
    }

    /*
     * 从钉钉获取用户详细信息
     *
     * @param accessToken 凭证
     * @param userId 用户ID
     */
    public DingUser getUser(String accessToken, String userId) throws ApiException {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/get");
        OapiV2UserGetRequest req = new OapiV2UserGetRequest();
        req.setUserid(userId);
        OapiV2UserGetResponse rsp = client.execute(req, accessToken);
        OapiV2UserGetResponse.UserGetResponse userGetResponse = rsp.getResult();
        if (userGetResponse == null) {
            throw new RuntimeException(rsp.getErrmsg());
        }
        DingUser user = new DingUser();
        BeanUtils.copyProperties(userGetResponse, user);
        return user;
    }

    /*
     * 从钉钉获取用户名称
     *
     * @param accessToken 凭证
     * @param userId 用户ID
     */
    public String getUserName(String accessToken, String userId) throws ApiException {
        return getUser(accessToken, userId).getName();
    }

    /**
     * 从钉钉获取主管理员ID
     */
    public String getAdminUserId(String accessToken) throws ApiException {
        List<OapiUserListadminResponse.ListAdminResponse> adminList = this.getAdminList(accessToken);
        if (adminList.isEmpty()) {
            return null;
        }
        return adminList.stream().findFirst().get().getUserid();
    }

    /**
     * 从钉钉获取管理员列表
     */
    public List<OapiUserListadminResponse.ListAdminResponse> getAdminList(String accessToken) throws ApiException {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/user/listadmin");
        OapiUserListadminRequest req = new OapiUserListadminRequest();
        OapiUserListadminResponse rsp = client.execute(req, accessToken);
        if (rsp.getResult() == null) {
            throw new RuntimeException(rsp.getErrmsg());
        }
        return rsp.getResult();
    }

    /**
     * 获取默认班次打卡时间
     */
    public ClockTimeVO getDefalutShiftClockTime(String accessToken) throws ApiException {
        ClockTimeVO clockTimeVO = new ClockTimeVO();
        String adminUserId = getAdminUserId(accessToken);
        List<DingShift> shiftList = getShiftList(accessToken, adminUserId);
        Optional<DingShift> shift = shiftList.stream().findFirst();
        OapiAttendanceShiftQueryResponse.TopShiftVo shiftInfo = getShiftInfo(accessToken, adminUserId, shift.get().getId());
        if (shiftInfo != null) {
            OapiAttendanceShiftQueryResponse.TopSectionVo topSectionVo = shiftInfo.getSections().get(0);
            topSectionVo.getPunches().forEach(topPunchVo -> {
                String time = DateUtil.format(topPunchVo.getCheckTime(), DatePattern.NORM_TIME_PATTERN);
                if (StrUtil.equals(topPunchVo.getCheckType(), CheckTypeEnum.ONDUTY.getMark())) {
                    clockTimeVO.setOnTime(time);
                }
                if (StrUtil.equals(topPunchVo.getCheckType(), CheckTypeEnum.OFFDUTY.getMark())) {
                    clockTimeVO.setOffTime(time);
                }
            });
        }
        return clockTimeVO;
    }

    public OapiAttendanceShiftQueryResponse.TopShiftVo getShiftInfo(String accessToken, String opUserId, Long shiftId) throws ApiException {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/attendance/shift/query");
        OapiAttendanceShiftQueryRequest req = new OapiAttendanceShiftQueryRequest();
        req.setOpUserId(opUserId);
        req.setShiftId(shiftId);
        OapiAttendanceShiftQueryResponse rsp = client.execute(req, accessToken);
        if (rsp.getResult() == null) {
            throw new RuntimeException(rsp.getErrmsg());
        }
        return rsp.getResult();
    }

    /**
     * 获取班次列表
     *
     * @param accessToken
     * @param opUserId    操作人ID
     * @return
     * @throws ApiException
     */
    public List<DingShift> getShiftList(String accessToken, String opUserId) throws ApiException {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/attendance/shift/list");
        OapiAttendanceShiftListRequest req = new OapiAttendanceShiftListRequest();
        req.setOpUserId(opUserId);
        OapiAttendanceShiftListResponse rsp = client.execute(req, accessToken);
        if (rsp.getResult() == null) {
            throw new RuntimeException(rsp.getErrmsg());
        }
        return rsp.getResult().getResult().stream().map(vo -> {
            DingShift shift = new DingShift();
            BeanUtils.copyProperties(vo, shift);
            return shift;
        }).collect(Collectors.toList());
    }
}
